const utils = {
  getValue(expr, vm) {
    return vm.$data[expr]
  },
  setValue(expr, vm, newValue) {
    vm.$data[expr] = newValue
  },
  model(node, value, vm) {
    const initValue = this.getValue(value, vm)
    node.addEventListener('input', e => {
      const newValue = e.target.value
      this.setValue(value, vm, newValue)
    })
  },
  text(node, value, vm) {},

  on(node, value, vm, eventName) {}
}

class Compiler {
  constructor(el, vm) {
    this.el = this.isElementNode(el) ? el : document.querySelector(el)
    this.vm = vm

    const fragment = this.compileFragment(this.el)
    this.compile(fragment)
  }

  compile(fragment) {
    const childNodes = Array.from(fragment.childNodes)
    childNodes.forEach(node => {
      if (this.isElementNode(node)) {
        // 标签接点h1/input读取属性，看是否有v-开头得属性
        this.compileElement(node)
      } else if (this.isTextNode(node)) {
        // 文本接点是否有双大括号接点
        this.compileText(node)
      }
      if (node.childNodes && node.childNodes.length) {
        this.compile(node)
      }
    })
  }

  compileElement(node) {
    // v-属性
    const attr = Array.from(node.attributes);
    attr.forEach(item => {
      const {name, value} = item
      if (this.isDirector(name)) {
        // 指令
        const [, directive] = name.split('-')
        const [compileKey, eventName] = directive.split(':')
        utils[compileKey](node, value, this.vm, eventName)
      }
    })
  }

  compileText(node) {
    // 匹配到 msg
    const content = node.textContent;
    if (/\{\{(.+)\}\}/.test(content)){
      console.log(content)
    }
  }

  isDirector(name) {
    return name.startsWith('v-')
  }

  compileFragment(el) {
    const f = document.createDocumentFragment()
    let firstChild;
    while (firstChild = el.firstChild) {
      f.appendChild(firstChild)
    }
    return f
  }

  isElementNode(el) {
    return el.nodeType === 1
  }

  isTextNode(el) {
    return el.nodeType === 3
  }
}

class Vue {
  constructor(options) {
    this.$el = options.el
    this.$data = options.data
    this.$options = options

    // 触发this.$data.xx 和模板绑定
    new Observer(this.$data)

    // 处理模板部分，将模板使用得data部分变量和模板进行绑定
    new Compiler(this.$el, this)
    this.proxyData(this.$data)
  }

  /**
   * 可以通过this.xx更改this.$data.xx得结果
   * @param data 属性data
   */
  proxyData(data) {
    Object.keys(data).forEach(key => {
      Object.defineProperty(this, key, {
        get() {
          return data[key]
        },
        set(v) {
          data[key] = v
        }
      })
    })
  }
}

class Observer {
  constructor(data) {
    this.observe(data)
  }

  observe(data) {
    if (data && typeof data === 'object') {
      Object.keys(data).forEach(key => {
        this.defineReactive(data, key, data[key])
      })
    }
  }

  defineReactive(object, key, value) {
    this.observe(value)
    let _this = this
    Object.defineProperty(object, key, {
      get() {
        return value
      },
      set(v) {
        if (value === v) return
        _this.observe(v)
        value = v
      }
    })
  }
}
